package server

import (
  "bufio"
  "bytes"
  "errors"
  "net"
  "runtime"
  "strings"

  "github.com/siddontang/go-log/log"
)

var errReadRequest = errors.New("invalid request protocol")

type respClient struct {
  app *App

  conn net.Conn
  rb   *bufio.Reader

  req *requestContext
}

type respWriter struct {
  buff *bufio.Writer
}

func newClientRESP(conn net.Conn, app *App) {
  c := new(respClient)

  c.app = app
  c.conn = conn

  c.rb = bufio.NewReaderSize(conn, 512)

  c.req = newRequestContext(app)
  c.req.resp = newWriterRESP(conn)
  c.req.remoteAddr = conn.RemoteAddr().String()

  go c.run()
}

func (c *respClient) run() {
  defer func() {
    if e := recover(); e != nil {
      buf := make([]byte, 4096)
      n := runtime.Stack(buf, false)
      buf = buf[0:n]
      log.Error("client run panic %s:%v", buf, e)
    }
    c.close()
  }()

  for {
    reqData, err := c.readRequest()
    if err != nil {
      log.Error("%v", err)
      return
    }

    c.handleRequest(reqData)
  }
}

func (c *respClient) close() {
  c.conn.Close()
  if c.req.nodeinfo != nil {
    c.req.nodeinfo.Off()
  }
  c.req.family = nil
  c.req.nodeinfo = nil
}

func (c *respClient) readLine() ([]byte, error) {
  return ReadLine(c.rb)
}

//A client sends to the server a RESP Array consisting of just Bulk Strings.
func (c *respClient) readRequest() ([][]byte, error) {
  l, err := c.readLine()
  if err != nil {
    return nil, err
  } else if len(l) == 0 {
    return nil, errReadRequest
  }

  req := bytes.Split(l, []byte(" "))
  if len(req) == 0 {
    return nil, errReadRequest
  }
  return req, nil
}

func (c *respClient) handleRequest(reqData [][]byte) {
  c.req.cmd = strings.ToLower(string(reqData[0]))
  c.req.args = reqData[1:]
  c.req.perform()
}

// response writer
func newWriterRESP(conn net.Conn) *respWriter {
  w := new(respWriter)
  w.buff = bufio.NewWriterSize(conn, 512)
  return w
}

func (w *respWriter) writeError(err error) {}

func (w *respWriter) writeBulk(b []byte) {
  w.buff.Write(b)
}

func (w *respWriter) flush() {
  w.buff.Flush()
}
